import os
import subprocess
import requests
import json
from typing import Dict
from pathlib import Path
from datetime import datetime
from .database import SessionLocal, DatabaseError

class DownloadManager:
    _instance = None
    _current_tasks = {}
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.max_concurrent = 3
        return cls._instance

    @classmethod
    def check_list_status(cls):
        instance = cls()
        db = SessionLocal()
        try:
            # 获取当前设置
            max_concurrent = int(db.execute(
                "SELECT value FROM settings WHERE key='maxConcurrentDownloads'"
            ).fetchone()[0])
            
            # 计算可启动的任务数
            current_running = len(cls._current_tasks)
            available_slots = max(0, max_concurrent - current_running)
            
            if available_slots > 0:
                pending_tasks = db.execute(
                    "SELECT * FROM downloads WHERE status='pending' LIMIT ?", 
                    (available_slots,)
                ).fetchall()
                
                for task in pending_tasks:
                    task_id = task['id']
                    cls._current_tasks[task_id] = {
                        'process': None,
                        'status': 'preparing'
                    }
                    # 启动下载线程
                    cls._instance._start_download_task(task)
                    
        except Exception as e:
            db.rollback()
            raise DatabaseError(f"检查任务状态失败: {str(e)}")
        finally:
            db.close()

    def _start_download_task(self, task: Dict):
        # 根据下载模式选择下载方式
        if task['download_mode'] == 'ffmpeg':
            self._download_ffmpeg(task)
        else:
            self._download_segmented(task)

    def _download_segmented(self, task: Dict):
        try:
            # 创建临时目录
            temp_dir = Path(task['save_dir']) / f"temp_{task['id']}"
            temp_dir.mkdir(exist_ok=True)
            
            # 解析m3u8文件
            m3u8_content = requests.get(task['url']).text
            segments = [line for line in m3u8_content.split('\n') if line.endswith('.ts')]
            
            # 生成分片列表文件
            list_file = temp_dir / f"{task['id']}.list"
            with open(list_file, 'w') as f:
                f.write('\n'.join(segments))
            
            # 更新任务状态
            self._update_task_status(task['id'], 'downloading')
            
            # 下载分片逻辑
            for idx, segment in enumerate(segments):
                if self._check_abort(task['id']):
                    break
                
                retry = 0
                while retry < 3:
                    try:
                        ts_path = temp_dir / f"{idx}.ts"
                        with requests.get(segment, stream=True) as r:
                            with open(ts_path, 'wb') as f:
                                for chunk in r.iter_content(chunk_size=8192):
                                    f.write(chunk)
                        break
                    except Exception as e:
                        retry += 1
                        if retry == 3:
                            self._cleanup_failure(task, temp_dir, f"分片下载失败: {str(e)}")
                            return
                
                # 更新进度
                progress = (idx + 1) / len(segments) * 100
                self._update_progress(task['id'], progress)

            # 合并文件逻辑
            if task['download_mode'] == 'segmented':
                self._merge_files(task, temp_dir, list_file)

            self._update_task_status(task['id'], 'completed')
            
        except Exception as e:
            import logging
            logging.error(f"分段下载失败[任务ID:{task['id']}]: {str(e)}", exc_info=True)
            self._cleanup_failure(task, temp_dir, f"分片下载失败: {str(e)}")
        finally:
            # 清理临时目录
            if temp_dir.exists():
                for f in temp_dir.glob('*'):
                    f.unlink()
                temp_dir.rmdir()

    def _download_ffmpeg(self, task: Dict):
        try:
            # 检查ffmpeg是否存在
            if not self._check_ffmpeg():
                raise Exception("FFmpeg未安装")
            
            # 生成输出路径
            filename = task['name'] or f"output_{datetime.now().timestamp()}.{task['format']}"
            output_path = Path(task['save_dir']) / filename
            
            # 构建ffmpeg命令
            cmd = [
                'ffmpeg',
                '-i', task['url'],
                '-c', 'copy',
                str(output_path)
            ]
            
            # 启动进程
            process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                universal_newlines=True
            )
            
            self._current_tasks[task['id']]['process'] = process
            self._update_task_status(task['id'], 'downloading')
            
            # 解析输出
            while True:
                if self._check_abort(task['id']):
                    process.kill()
                    break
                    
                output = process.stdout.readline()
                if output == '' and process.poll() is not None:
                    break
                
                # 解析进度信息（需要根据实际输出格式调整）
                if 'time=' in output:
                    time_str = output.split('time=')[1].split()[0]
                    # 转换时间为秒数并计算进度
                    total_secs = sum(int(x) * 60 ** i for i, x in enumerate(reversed(time_str.split(':'))))
                    progress = (total_secs / 3600) * 100  # 假设总时长为1小时
                    self._update_progress(task['id'], progress)
                    
            if process.returncode == 0:
                self._update_task_status(task['id'], 'completed')
            else:
                raise Exception(f"FFmpeg错误: 退出码 {process.returncode}")
                
        except Exception as e:
            import logging
            logging.error(f"FFmpeg下载失败[任务ID:{task['id']}]: {str(e)}", exc_info=True)
            self._update_task_status(task['id'], 'error', str(e))

    def _update_task_status(self, task_id: int, status: str, message: str = ""):
        """更新任务状态到数据库"""
        db = SessionLocal()
        try:
            db.execute(
                "UPDATE downloads SET status = ?, message = ?, updatedtime = CURRENT_TIMESTAMP WHERE id = ?",
                (status, message, task_id)
            )
            db.commit()
        except Exception as e:
            db.rollback()
            raise DatabaseError(f"状态更新失败: {str(e)}")
        finally:
            db.close()

    def _update_progress(self, task_id: int, progress: float, completed: int, total: int, speed: float):
        """更新进度信息"""
        db = SessionLocal()
        try:
            db.execute(
                '''
                UPDATE downloads 
                SET progress = ?, completed = ?, total = ?, speed = ?, updatedtime = CURRENT_TIMESTAMP 
                WHERE id = ?
                ''',
                (round(progress, 2), completed, total, round(speed, 2), task_id)
            )
            db.commit()
        except Exception as e:
            db.rollback()
            raise DatabaseError(f"进度更新失败: {str(e)}")
        finally:
            db.close()

    def _check_abort(self, task_id: int) -> bool:
        """检查是否需要中止任务"""
        db = SessionLocal()
        try:
            task = db.execute(
                "SELECT status FROM downloads WHERE id = ?", 
                (task_id,)
            ).fetchone()
            
            if task and task['status'] in ('stopped', 'deleted', 'error'):
                # 中止后清理资源
                if task_id in self._current_tasks:
                    process = self._current_tasks[task_id]['process']
                    if process:
                        process.kill()
                    del self._current_tasks[task_id]
                return True
            return False
        finally:
            db.close()

    def _merge_files(self, task: Dict, temp_dir: Path, list_file: Path):
        """合并分片文件"""
        try:
            output_path = Path(task['save_dir']) / (task['name'] or f"output_{datetime.now().timestamp()}.{task['format']}")
            # 合并时状态
            self._update_task_status(task['id'], 'merging')
            # Windows系统使用copy /b命令合并
            if os.name == 'nt':
                with open(list_file, 'w') as f:
                    for ts_file in temp_dir.glob('*.ts'):
                        f.write(f"file '{ts_file}'\n")
                
                merge_cmd = [
                    'ffmpeg',
                    '-f', 'concat',
                    '-safe', '0',
                    '-i', str(list_file),
                    '-c', 'copy',
                    str(output_path)
                ]
            else:
                merge_cmd = f"cat {temp_dir}/*.ts > {output_path}"
            
            subprocess.run(merge_cmd, check=True)
           # 合并完成后删除临时文件
            for f in temp_dir.glob('*'):
                f.unlink()
            self._update_task_status(task['id'], 'completed')
            
        except subprocess.CalledProcessError as e:
            self._cleanup_failure(task, temp_dir, f"文件合并失败: {str(e)}")
            raise

    def _cleanup_failure(self, task: Dict, temp_dir: Path, error_msg: str):
        """清理失败任务"""
        if temp_dir.exists():
            for f in temp_dir.glob('*'):
                f.unlink()
            temp_dir.rmdir()
        self._update_task_status(task['id'], 'error', error_msg)

    def _check_ffmpeg(self) -> bool:
        """检查FFmpeg是否安装"""
        try:
            if os.name == 'nt':
                subprocess.run(['where', 'ffmpeg'], check=True, stdout=subprocess.DEVNULL)
            else:
                subprocess.run(['which', 'ffmpeg'], check=True, stdout=subprocess.DEVNULL)
            return True
        except subprocess.CalledProcessError:
            return False